package codegenjvm;

/*	Alexander Sedgwick
	Rebeen Hawramy
*/

import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Stack;

import ast.*;
import util.ClassTreeNode;
import util.SymbolTable;
import visitor.Visitor;
public class CodeGenVisitor extends Visitor 
{
	public static ClassTreeNode root;
	
	VirtualCodeGenEnv vcge;
	
	
	private int labelNum; //per method or per class?
	private ClassTreeNode classNode;
	private SymbolTable mt;
	private String className;
	private String parentClassName;
	private String currentMethodType;
	//private String methodHolder;
	
	private ArrayList<String> lineList;
	
	public CodeGenVisitor(ClassTreeNode classNode) {
		this.classNode = classNode;
		mt = classNode.getMethodSymbolTable();
		className = classNode.getName();
		//Get parentClassName
		parentClassName = classNode.getASTNode().getParent();
		parentClassName = (parentClassName.equals("Object")) 
				? "java/lang/Object" : parentClassName ;
		
		lineList = new ArrayList<String>();
		labelNum = 1;
	}
	
	
	/** Visit a class node
     * @param node the class node
     * @return result of the visit 
     * */
   public Object visit(Class_ node) {
	   //Header
	   System.out.println("********************** CG : " + className + " ********************");
	   println(".source " + node.getFilename());
	   println(".class public " + node.getName());
	   println(".super " + parentClassName);
	   println(".implements java/lang/Cloneable");
	   //Traverse Fields and Methods
	   try {
		   MemberList ml = node.getMemberList();
		   ml.accept(this);
	   } catch (Exception e) {
		   System.out.println("Error : " + e.getMessage());
		   StackTraceElement [] stElements = e.getStackTrace();
		   for(StackTraceElement ste : stElements) {
			   System.out.println(ste);
		   }
		   //Print out lineList
		   try {
			  	FileOutputStream fstream; 
				DataOutputStream out;
				BufferedWriter bw;
				fstream = new FileOutputStream(classNode.getName() + ".out", false);
				out = new DataOutputStream(fstream);
				bw = new BufferedWriter(new OutputStreamWriter(out));
				
				for(int i = 0; i < lineList.size(); i++) {
					bw.write(lineList.get(i) + "\n");
				}
				bw.close();
				out.close();
				fstream.close();
		 	}catch (Exception ex){//Catch exception if any
				System.err.println("Read Error : " + ex.getMessage());
				StackTraceElement [] stElements2 = ex.getStackTrace();
				for(StackTraceElement ste : stElements2) {
					System.out.println(ste);
				}
		    }
	   }
	   
	   System.out.println("********************** Write to " + className + ".j ********************");
	   //Return instructions
	   return lineList;
   }

   /** Visit a list node of members
     * @param node the member list node
     * @return result of the visit 
     * */
   public Object visit(MemberList node) {
	   
	   Iterator<ASTNode> members = node.getIterator();
	   ArrayList<Field> fields = new ArrayList<Field>();
	   ArrayList<Method> methods = new ArrayList<Method>();
	   
	   ASTNode member;
	   while(members.hasNext())	{
		   member = members.next();
		   if(member instanceof Field) {
			   fields.add((Field)member);
		   } else { //Method
			   methods.add((Method)member);
		   }
	   }
	   //Create Virtual Class Environment
	   vcge = new VirtualCodeGenEnv();
	   vcge.enterScope();
	   //Add class to locals
	   vcge.addLocal("this", classNode.getName());
	   
	   //visit fields
	   for(int i = 0; i < fields.size(); i++) {
		   fields.get(i).accept(this);
	   }
	   println("");
	   
	   //Default Constructor header
	   println(".method public <init>()V");
	   
	   
	   for(int i =0; i < fields.size(); i++) {
		   if(fields.get(i).getInit() != null) {
			   storeIn(fields.get(i).getName(), fields.get(i).getType(),
					   true, fields.get(i).getInit());
		   }
	   }
	   
	   //Call parent constructor
	   printlnTab("aload_0");
	   vcge.push("this");
	   
	   if(parentClassName.equals("Object")) {
		   printlnTab("invokespecial java/lang/" 
				   + parentClassName + "/<init>()V");
	   } else {
		   printlnTab("invokespecial " + parentClassName + "/<init>()V");
	   }
	   vcge.pop();
	   
	 //Find the constructor header and add the limits after that index
	   int limitsIndex = lineList.lastIndexOf(".method public <init>()V") + 1;
	   lineList.add(limitsIndex,"      .limit stack " + vcge.getMaxOpSize());
	   lineList.add(limitsIndex+1,"      .limit locals " + vcge.getMaxLocSize());
	   
	   printlnTab("return");
	   println(".end method");
	   println("");
	   
	   //visit methods...
	   for(int i = 0; i < methods.size(); i++) {
		   methods.get(i).accept(this);
	   }
	   
	   return null;
   }

   /** Visit a field node
     * @param node the field node
     * @return result of the visit 
     * */
   public Object visit(Field node) {
	   println(".field protected " + 
			   node.getName() + " " +
			   getTypeDescriptor(node.getType()));
	   vcge.addLocal(node.getName(),node.getType());
	   return null;
   }

   /** Visit a method node
     * @param node the method node
     * @return result of the visit 
     * */
   public Object visit(Method node) {
	   vcge.enterNewMethod();
	   String header;
	   if(node.getName().equals("main")) {
		   //split Main into Main and original_main
		   
		   //Main
		   println(".method static public main([Ljava.lang.String;)V");
		   printlnTab(".limit stack 2");
		   printlnTab(".limit locals 1");
		   printlnTab("new Main");
		   printlnTab("dup");
		   printlnTab("invokespecial Main/<init>()V");
		   printlnTab("invokevirtual Main/original_main()V");
		   printlnTab("return");
		   println(".end method");
		   println("");
		   
		   //Original Main
		   header = ".method public original_main()V";
		   
		   
		   
	   } else { //do normal method
		   header = (String)node.getFormalList().accept(this);
		   header = ".method public " + node.getName() 
		   		+ header
		   		+ getHeaderTypeDescriptor(node.getReturnType());
	   }
	   currentMethodType = node.getReturnType();
	   //Add to List
	   println(header);
	   println(".throws java/lang/CloneNotSupportedException");
	   node.getStmtList().accept(this);
	   println(".end method");
	   println("");
	   
	   int limitsIndex = lineList.lastIndexOf(header) + 2;
	   lineList.add(limitsIndex,"      .limit stack " + vcge.getMaxOpSize());
	   lineList.add(limitsIndex+1,"      .limit locals " + vcge.getMaxLocSize());
	   
	   //reset Limits
	   vcge.exitNewMethod();
	   return null;
   }
   
    /** Visit a list node of formals
      * @param node the formal list node
      * @return result of the visit 
      * */
    public Object visit(FormalList node) { 
    	String header = "(";
    	Iterator<ASTNode> formals = node.getIterator();
    	while(formals.hasNext()) {
    		header += formals.next().accept(this);
    	}
    	return header + ")";
    }

    /** Visit a formal node
      * @param node the formal node
      * @return result of the visit 
      * */
    public Object visit(Formal node) {
    	vcge.addLocal(node.getName(), node.getType());
    	return getHeaderTypeDescriptor(node.getType());
    }

    /** Visit a list node of statements
      * @param node the statement list node
      * @return result of the visit 
      * */
    public Object visit(StmtList node) {
    	Iterator<ASTNode> stmt = node.getIterator();
    	while(stmt.hasNext()) {
    		stmt.next().accept(this);
    	}
    	return null;
    }

    /** Visit a declaration statement node
      * @param node the declaration statement node
      * @return result of the visit 
      * */
    public Object visit(DeclStmt node) { 
    	
    	vcge.addLocal(node.getName(), node.getType());
    	if(node.getInit() != null) {
    		storeIn(node.getName(), node.getType(), false, node.getInit());
    	}
    	
    	return null;
    }

    /** Visit an expression statement node
      * @param node the expression statement node
      * @return result of the visit 
      * */
    public Object visit(ExprStmt node) {  
    	node.getExpr().accept(this);
    	while(!vcge.stackIsEmpty()) {
    		System.out.println("0");
    		vcge.pop();
    		printlnTab("pop");
    	}
    	return null;
    }

    /** Visit an if statement node
      * @param node the if statement node
      * @return result of the visit 
      * */
    public Object visit(IfStmt node) {
    	vcge.enterScope();
    	
    	String label1 = getNextLabel();
    	String label2 = getNextLabel();
    	System.out.println("; if statement condition");
    	
    	node.getPredExpr().accept(this);
    	printlnTab("ifeq" + label1);
    	vcge.pop();
    	node.getThenStmt().accept(this);
    	printlnTab("goto" + label2);
    	println("    " + label1 + ":");
    	node.getElseStmt().accept(this);
    	println("    " + label2 + ":");
    	
    	vcge.exitScope();
    	return null;
    }

    private String getNextLabel() {
		return "L" + labelNum++;
	}


	/** Visit a while statement node
      * @param node the while statement node
      * @return result of the visit 
      * */
    public Object visit(WhileStmt node) {
    	vcge.enterScope();
    	
    	String label1 = getNextLabel();
    	String label2 = getNextLabel();
    	println("; while statement goto");
    	
    	printlnTab("goto " + label1);
    	println("    " + label2 + ":");
    	node.getBodyStmt().accept(this);
    	
    	println("    " + label1 + ":");
    	println("; while statement coniditon");
    	node.getPredExpr().accept(this);
    	printlnTab("ifne " + label2); //is it ne or eq?
    	vcge.pop();
    	
    	vcge.exitScope();
    	return null;
    }

    /** Visit a for statement node
      * @param node the for statement node
      * @return result of the visit 
      * */
    public Object visit(ForStmt node) { 
    	vcge.enterScope();
    	
    	String label1 = getNextLabel();
    	String label2 = getNextLabel();
    	
    	node.getInitExpr().accept(this);
    	printlnTab("pop");
    	vcge.pop();
    	
    	println("; for statement goto");
    	printlnTab("goto " + label1);
    	println("    " + label2 + ":");
    	node.getBodyStmt().accept(this);
    	node.getUpdateExpr();
    	printlnTab("pop");
    	vcge.pop();
    	
    	println("    " + label1 + ":");
    	println("; for statement condition");
    	node.getPredExpr().accept(this);
    	printlnTab("ifne " + label2); //is it ne or eq?
    	vcge.pop();
    	
    	vcge.exitScope();
    	return null;
    }

    /** Visit a block statement node
      * @param node the block statement node
      * @return result of the visit 
      * */
    public Object visit(BlockStmt node) {
    	vcge.enterScope();
    	node.getStmtList().accept(this);
    	vcge.exitScope();
    	return null;
    }

    /** Visit a return statement node
      * @param node the return statement node
      * @return result of the visit 
      * */
    public Object visit(ReturnStmt node) {
    	if(node.getExpr() != null) 
    		node.getExpr().accept(this);
    	
    	if(currentMethodType.equals("void")) {
 		   printlnTab("return");
    	} else if(isPrimitive(currentMethodType)) {
    		printlnTab("ireturn");
    		vcge.pop();
 	   	} else {
 	   		printlnTab("areturn");
 	   		vcge.pop();
 	   	}
    	return null;
    }

    /** Visit a list node of expressions
      * @param node the expression list node
      * @return result of the visit 
      * */
    public Object visit(ExprList node) {
    	Iterator<ASTNode> expressions = node.getIterator();
    	int i = 0;
    	while(expressions.hasNext()) {
    		i++;
    		expressions.next().accept(this);
    	}
    	return i;
    }

    /** Visit a dispatch expression node
      * @param node the dispatch expression node
      * @return result of the visit 
      * */
    public Object visit(DispatchExpr node) { 
    	boolean isSuper = false;
    	//Push arguments onto stack
    	int size = (Integer)node.getActualList().accept(this);
    	//this should put the object reference on the stack
    	String methHeader = (String)node.getRefExpr().accept(this);
    	
    	/*--will only work if
    	 *	----1. getRefExpr always returns the Class Name of the reference
    	 *	---------These visit(Expr)s should return the class name
    	 *	------------NewExpr...
    	 *	------------AssignExpr...
    	 *	------------VarExpr...
    	 *	------------ArrayExpr...
    	 *	------------CastExpr...
    	 */
    	Expr ref = node.getRefExpr();
    	if(ref instanceof VarExpr) {
    		isSuper = ((VarExpr)ref).getName().equals("super");
    	}
    	methHeader = getMethod(methHeader, node.getMethodName());
    	if(isSuper) {
    		printlnTab("invokespecial " + methHeader);
    	} else {
    		printlnTab("invokevirtual " + methHeader);
    	}
    	//Does the Object ref need to be popped?
    	vcge.pop();
    	for(int i = size; i <= 0; i--) {
    		vcge.pop();
    	}
    	if(!methHeader.contains(")V")) {
    		vcge.push(methHeader);
    	}
    	return null;
    }

    /** Visit a new expression node
      * @param node the new expression node
      * @return result of the visit 
      * */
    public Object visit(NewExpr node) {  
    	
    	printlnTab("new " + getTypeDescriptor(node.getType()));
    	printlnTab("dup");
    	printlnTab("invokespecial " + getTypeDescriptor(node.getType()) +
    			"/<init>()V");
    	return node.getType();
    }

    /** Visit a new array expression node
      * @param node the new array expression node
      * @return result of the visit 
      * */
    public Object visit(NewArrayExpr node) {  
    	node.getSize().accept(this);
    	String type = node.getType().replace("[]","");
    	if(isPrimitive(type)) {
    		printlnTab("newarray " + getTypeDescriptor(type));
    	} else {
    		printlnTab("anewarray " + getTypeDescriptor(type));
    	}
    	return node.getType();
    }

    /** Visit an instanceof expression node
      * @param node the instanceof expression node
      * @return result of the visit 
      * */
    public Object visit(InstanceofExpr node) { 
    	node.getExpr().accept(this);
    	printlnTab("instanceof " + getTypeDescriptor(node.getType()));
    	vcge.pop();
    	vcge.push("instanceof " + node.getType());
    	return null;
    }

    /** Visit a cast expression node
      * @param node the cast expression node
      * @return result of the visit 
      * */
    public Object visit(CastExpr node) { 
    	node.getExpr().accept(this);
    	printlnTab("checkcast " + getTypeDescriptor(node.getType()));
    	vcge.pop();
    	vcge.push("cast " + node.getType());
    	return node.getType();
    }

    /** Visit an assignment expression node
      * @param node the assignment expression node
      * @return result of the visit 
      * */
    public Object visit(AssignExpr node) {
    	boolean isField = (node.getRefName() != null 
    			&& node.getRefName().equals("this"));
    	storeIn(node.getName(), vcge.getLocalType(node.getName(),isField) 
    			,isField, node.getExpr());
    	return vcge.getLocalType(node.getName(), isField);
    }

    /** Visit an array assignment expression node
      * @param node the array assignment expression node
      * @return result of the visit 
      * */
    public Object visit(ArrayAssignExpr node) { 
    	boolean isField = (node.getRefName() == null);
    	
    	//load array from Local Stack
    	loadFrom(node.getName(), isField);
    	//put index on stack
    	node.getIndex().accept(this);
    	//put value on stack
    	node.getExpr().accept(this);
    	
    	printlnTab("dup_2x");
    	//Get Array Type
    	String type = vcge.getLocalType(node.getName(), isField)
    			.replace("[]", "");
    	
    	if(isPrimitive(type)) {
    		printlnTab("iastore");
    	} else {
    		printlnTab("aastore");
    	}
    	vcge.pop();
    	vcge.pop();
    	vcge.pop();
    	vcge.push("dup_2x arrval");
    	return null;
    }
    /** Visit a binary comparison equals expression node
      * @param node the binary comparison equals expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompEqExpr node) {  
    	String label1 = getNextLabel();
		String label2 = getNextLabel();
		node.getLeftExpr().accept(this);
		
		boolean isField = (((VarExpr)node.getLeftExpr()).getRef() != null) && 
						((VarExpr)node.getLeftExpr()).getRef().equals("this");
    	if(node.getLeftExpr() instanceof VarExpr && 
				!isPrimitive(vcge.getLocalType(((VarExpr)node.getLeftExpr()).getName(), isField ))) {
			node.getRightExpr().accept(this);
			printlnTab("if_acmpne " + label1);
		}	else {
			node.getRightExpr().accept(this);
			printlnTab("if_icmpne " + label1);
		}
		printlnTab("iconst_1");
		printlnTab("goto " + label2);
		println("    " + label1 + ":");
		printlnTab("iconst_0");
		println("    " + label2 + ":");
		vcge.pop();
		vcge.pop();
		vcge.push("EQ val");
    	return null;
    }

    /** Visit a binary comparison not equals expression node
      * @param node the binary comparison not equals expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompNeExpr node) {  
    	String label1 = getNextLabel();
    	String label2 = getNextLabel();
    	node.getLeftExpr().accept(this);
    	
    	
    	boolean isField = (((VarExpr)node.getLeftExpr()).getRef() != null) && 
					((VarExpr)node.getLeftExpr()).getRef().equals("this");
    	if(node.getLeftExpr() instanceof VarExpr && 
				!isPrimitive(vcge.getLocalType(((VarExpr)node.getLeftExpr()).getName(), isField ))) {
			node.getRightExpr().accept(this);
			printlnTab("if_acmpeq " + label1);
		}	else {
			node.getRightExpr().accept(this);
			printlnTab("if_icmpeq " + label1);
		}
	    	
    	printlnTab("iconst_1");
    	printlnTab("goto " + label2);
    	println("    " + label1 + ":");
    	printlnTab("iconst_0");
    	println("    " + label2 + ":");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("NE val");
    	return null;
    }

    /** Visit a binary comparison less than expression node
      * @param node the binary comparison less than expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompLtExpr node) {  
	String label1 = getNextLabel();
    	String label2 = getNextLabel();
    	node.getLeftExpr().accept(this);
	node.getRightExpr().accept(this);
    	printlnTab("if_icmpge " + label1);
    	printlnTab("iconst_1");
    	printlnTab("goto " + label2);
    	println("    " + label1 + ":");
    	printlnTab("iconst_0");
    	println("    " + label2 + ":");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("CompGt val");

    	return null;
    }

    /** Visit a binary comparison less than or equal to expression node
      * @param node the binary comparison less than or equal to expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompLeqExpr node) {
    	String label1 = getNextLabel();
    	String label2 = getNextLabel();
    	node.getLeftExpr().accept(this);
	node.getRightExpr().accept(this);
    	printlnTab("if_icmpgt " + label1);
    	printlnTab("iconst_1");
    	printlnTab("goto " + label2);
    	println("    " + label1 + ":");
    	printlnTab("iconst_0");
    	println("    " + label2 + ":");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("CompGt val");

    	return null;
    }

    /** Visit a binary comparison greater than expression node
      * @param node the binary comparison greater than expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompGtExpr node) {
    	String label1 = getNextLabel();
    	String label2 = getNextLabel();
    	node.getLeftExpr().accept(this);
	node.getRightExpr().accept(this);
    	printlnTab("if_icmple " + label1);
    	printlnTab("iconst_1");
    	printlnTab("goto " + label2);
    	println("    " + label1 + ":");
    	printlnTab("iconst_0");
    	println("    " + label2 + ":");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("CompGt val");

    	return null;
    }

    /** Visit a binary comparison greater than or equal to expression node
      * @param node the binary comparison greater to or equal to expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompGeqExpr node) {
	String label1 = getNextLabel();
    	String label2 = getNextLabel();
    	node.getLeftExpr().accept(this);
	node.getRightExpr().accept(this);
    	printlnTab("if_icmplt " + label1);
    	printlnTab("iconst_1");
    	printlnTab("goto " + label2);
    	println("    " + label1 + ":");
    	printlnTab("iconst_0");
    	println("    " + label2 + ":");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("CompGeq val");

    	return null;
    }

    /** Visit a binary arithmetic plus expression node
      * @param node the binary arithmetic plus expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryArithPlusExpr node) {  
    	node.getLeftExpr().accept(this);
    	node.getRightExpr().accept(this);
    	printlnTab("iadd");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("ADD val");
    	return null;
    }

    /** Visit a binary arithmetic minus expression node
      * @param node the binary arithmetic minus expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryArithMinusExpr node) {
    	node.getLeftExpr().accept(this);
    	node.getRightExpr().accept(this);
    	printlnTab("isub");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("SUB val");
    	return null;
    }

    /** Visit a binary arithmetic times expression node
      * @param node the binary arithmetic times expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryArithTimesExpr node) {
    	node.getLeftExpr().accept(this);
    	node.getRightExpr().accept(this);
    	printlnTab("imul");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("MUL val");
    	return null;
    }

    /** Visit a binary arithmetic divide expression node
      * @param node the binary arithmetic divide expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryArithDivideExpr node) {  
    	node.getLeftExpr().accept(this);
    	node.getRightExpr().accept(this);
    	printlnTab("idiv");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("DIV val");
    	return null;
    }

    /** Visit a binary arithmetic modulus expression node
      * @param node the binary arithmetic modulus expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryArithModulusExpr node) {
    	node.getLeftExpr().accept(this);
    	node.getRightExpr().accept(this);
    	printlnTab("irem");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("MOD val");
    	return null;
    }


    /** Visit a binary logical AND expression node
      * @param node the binary logical AND expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryLogicAndExpr node) {
    	String label1 = getNextLabel();
    	String label2 = getNextLabel();
    	node.getLeftExpr().accept(this);
    	printlnTab("ifne " + label1);
    	printlnTab("iconst_0");
    	printlnTab("goto " + label2);
    	println("    " + label1 + ":");
    	node.getRightExpr().accept(this);
    	println("    " + label2 + ":");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("AND val");
    	return null;
    }

    /** Visit a binary logical OR expression node
      * @param node the binary logical OR expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryLogicOrExpr node) {
    	String label1 = getNextLabel();
    	String label2 = getNextLabel();
    	node.getLeftExpr().accept(this);
    	printlnTab("ifeq " + label1);
    	printlnTab("iconst_1");
    	printlnTab("goto " + label2);
    	println("    " + label1 + ":");
    	node.getRightExpr().accept(this);
    	println("    " + label2 + ":");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("OR val");
    	return null;
    }

    /** Visit a unary negation expression node
      * @param node the unary negation expression node
      * @return result of the visit 
      * */
    public Object visit(UnaryNegExpr node) {
    	node.getExpr().accept(this);
    	printlnTab("ineg");
  	   	return null;
     }

    /** Visit a unary NOT expression node
      * @param node the unary NOT expression node
      * @return result of the visit 
      * */
    public Object visit(UnaryNotExpr node) { 
    	printlnTab("iconst_0");
    	vcge.push("iconst_0");
    	node.getExpr().accept(this);
    	printlnTab("ixor");
    	vcge.pop();
    	vcge.pop();
    	vcge.push("Not");
 	   return null;
    }

    /** Visit a unary increment expression node
      * @param node the unary increment expression node
      * @return result of the visit 
      * */
    public Object visit(UnaryIncrExpr node) {
 	   //node.getExpr().accept(this);
 	   ASTNode innernode = node.getExpr();
 	   
	 	 //if infix
	 	 if(!node.isPostfix()){
	 		if(innernode instanceof VarExpr){
	 		   	//if Variable
	 		   	int index = vcge.getLocalOffset(((VarExpr) innernode).getName(), false);
	 		   	
	 		   	
	 			printlnTab("iinc "+index+" 1");
	 			
	 			//Keep variable on virtual Stack
	 			//But remove previous load
	 			removeLastPrint();
	 			//Loads on expression visit
	 			if(index<4){
	 				printlnTab("iload_"+index);
	 		   	}else if(index >= 4){
	 		   		printlnTab("iload "+index);
	 		   	}
	 	   	}else if(innernode instanceof ArrayExpr){ 
	 	   		//if Array
	 	   		int index;
	 	   		
	 	   		//check if Class field or method variable. to read in the correct offset. 
	 	   		if(((ArrayExpr) innernode).getRef().equals("this")){
	 	   			index = vcge.getLocalOffset(((ArrayExpr) innernode).getName(),true);
	 	   		}else{
	 	   			index = vcge.getLocalOffset(((ArrayExpr) innernode).getName(),false);
	 	   		}
	 	   		
	 			if(index<4){
	 				printlnTab("aload_"+index);
	 				vcge.push("aload_"+index);
	 		   	}else if(index >= 4){
	 		   		printlnTab("aload "+index);
	 				vcge.push("aload "+index);
	 		   	}
	 	   		
	 	   		//push index
	 	   		Expr arrayindex = (Expr) ((ArrayExpr) innernode).getIndex().accept(this);
	 	   		
	 	   		int arrindxsize = Integer.parseInt(arrayindex.toString());
	 	   		
	 	   		if(arrindxsize < 6){
	 		   		printlnTab("iconst_"+arrayindex.toString());
	 				vcge.push("iconst_"+arrayindex.toString());
	 	   		}else{
	 	   			printlnTab("iconst "+arrayindex.toString());
	 				vcge.push("iconst "+arrayindex.toString());
	 	   		}
	 			
	 			printlnTab("dup");
	 			
	 			//need to type check...
	 			//use correct load....
	 			if(isPrimitive(((ArrayExpr) innernode).getExprType())){
	 				printlnTab("iaload "+index);
	 	   		}else{
	 	   			printlnTab("aaload "+index);
	 	   		} 
	 				
	
	 			printlnTab("iconst_1");
	 			vcge.push("iconst_1");	
	 			
	 			printlnTab("iadd");
	 			vcge.push("iadd"); 
	 			
	 			printlnTab("dup_x2");
	 			vcge.push("dup_x2");
	
	 			vcge.pop();
	 			vcge.pop();
	 			vcge.pop();
	 			
	 			printlnTab("iastore "+index);
	
	 	   	}
	 	 }else{//if postfix
	 		
	 		if(innernode instanceof VarExpr){
	 		   	//if Variable
	 		   	int index = vcge.getLocalOffset(((VarExpr) innernode).getName(), false);
	 		   
	 		    loadFrom(((VarExpr) innernode).getName(),false);
	 		   	printlnTab("iinc "+index+" 1");
	 	   	}else if(innernode instanceof ArrayExpr){ 
	 		   	//if Array
	 	   		int index;
	 	   		
	 	   		//check if Class field or method variable. to read in the correct offset. 
	 	   		if(((ArrayExpr) innernode).getRef().equals("this")){
	 	   			index = vcge.getLocalOffset(((ArrayExpr) innernode).getName(),true);
	 	   		}else{
	 	   			index = vcge.getLocalOffset(((ArrayExpr) innernode).getName(),false);
	 	   		}
	 	   		
	// 			if(index<4){
	// 				printlnTab("aload_"+index);
	// 				vcge.push("aload_"+index);
	// 		   	}else if(index >= 4){
	// 		   		printlnTab("aload "+index);
	// 				vcge.push("aload "+index);
	// 		   	}
	 	   		
	 	   		//push index
	 	   		Expr arrayindex = (Expr) ((ArrayExpr) innernode).getIndex().accept(this);
	 	   		
	 	   		int arrindxsize = Integer.parseInt(arrayindex.toString());
	 	   		
	 	   		if(arrindxsize < 6){
	 		   		printlnTab("iconst_"+arrayindex.toString());
	 				vcge.push("iconst_"+arrayindex.toString());
	 	   		}else{
	 	   			printlnTab("iconst "+arrayindex.toString());
	 				vcge.push("iconst "+arrayindex.toString());
	 	   		}
	 			
	 			printlnTab("dup");
	 			
	 			//need to type check...
	 			//use correct load....
	 			if(isPrimitive(((ArrayExpr) innernode).getExprType())){
	 				printlnTab("iaload "+index);
	 	   		}else{
	 	   			printlnTab("aaload "+index);
	 	   		} 
	 				
	 			printlnTab("dup_x2");
	 			vcge.push("dup_x2");
	
	 			printlnTab("iconst_1");
	 			vcge.push("iconst_1");	
	 			
	 			printlnTab("iadd");
	 			vcge.push("iadd"); 
	
	 			vcge.pop();
	 			vcge.pop();
	 			vcge.pop();
	 			
	 			printlnTab("iastore "+index);
	 	   	}
	 	 }
    	
	 	 return null;
    }

    /** Visit a unary decrement expression node
     * @param node the unary decrement expression node
     * @return result of the visit 
     * */
   public Object visit(UnaryDecrExpr node) { 
	   node.getExpr().accept(this);
 	   ASTNode innernode = node.getExpr();
 	   
	 	 //if infix
	 	 if(!node.isPostfix()){
	 		if(innernode instanceof VarExpr){
	 		   	//if Variable
	 		   	int index = vcge.getLocalOffset(((VarExpr) innernode).getName(), false);
	 		   	
	 		   	
	 			printlnTab("iinc "+index+" -1");
	 			
	 			//Keep variable on virtual Stack
	 			//But remove previous load
	 			removeLastPrint();
	 			//Loads on expression visit
	 			if(index<4){
	 				printlnTab("iload_"+index);
	 		   	}else if(index >= 4){
	 		   		printlnTab("iload "+index);
	 		   	}
	 	   	}else if(innernode instanceof ArrayExpr){ 
	 	   		//if Array
	 	   		int index;
	 	   		
	 	   		//check if Class field or method variable. to read in the correct offset. 
	 	   		if(((ArrayExpr) innernode).getRef().equals("this")){
	 	   			index = vcge.getLocalOffset(((ArrayExpr) innernode).getName(),true);
	 	   		}else{
	 	   			index = vcge.getLocalOffset(((ArrayExpr) innernode).getName(),false);
	 	   		}
	 	   		
	// 			if(index<4){
	// 				printlnTab("aload_"+index);
	// 				vcge.push("aload_"+index);
	// 		   	}else if(index >= 4){
	// 		   		printlnTab("aload "+index);
	// 				vcge.push("aload "+index);
	// 		   	}
	 	   		
	 	   		//push index
	 	   		Expr arrayindex = (Expr) ((ArrayExpr) innernode).getIndex().accept(this);
	 	   		
	 	   		int arrindxsize = Integer.parseInt(arrayindex.toString());
	 	   		
	 	   		if(arrindxsize < 6){
	 		   		printlnTab("iconst_"+arrayindex.toString());
	 				vcge.push("iconst_"+arrayindex.toString());
	 	   		}else{
	 	   			printlnTab("iconst "+arrayindex.toString());
	 				vcge.push("iconst "+arrayindex.toString());
	 	   		}
	 			
	 			printlnTab("dup");
	 			
	 			//need to type check...
	 			//use correct load....
	 			if(isPrimitive(((ArrayExpr) innernode).getExprType())){
	 				printlnTab("iaload "+index);
	 	   		}else{
	 	   			printlnTab("aaload "+index);
	 	   		} 
	 				
	
	 			printlnTab("iconst -1");
	 			vcge.push("iconst -1");	
	 			
	 			printlnTab("iadd");
	 			vcge.push("iadd"); 
	 			
	 			printlnTab("dup_x2");
	 			vcge.push("dup_x2");
	
	 			vcge.pop();
	 			vcge.pop();
	 			vcge.pop();
	 			
	 			printlnTab("iastore "+index);
	
	 	   	}
	 	 }else{//if postfix
	 		
	 		if(innernode instanceof VarExpr){
	 		   	//if Variable
	 		   	int index = vcge.getLocalOffset(((VarExpr) innernode).getName(), false);
	 		   
	 		   	loadFrom(((VarExpr) innernode).getName(),false);
	 		   	printlnTab("iinc "+index+" -1"); 
	 	   	}else if(innernode instanceof ArrayExpr){ 
	 		   	//if Array
	 	   		int index;
	 	   		
	 	   		//check if Class field or method variable. to read in the correct offset. 
	 	   		if(((ArrayExpr) innernode).getRef().equals("this")){
	 	   			index = vcge.getLocalOffset(((ArrayExpr) innernode).getName(),true);
	 	   		}else{
	 	   			index = vcge.getLocalOffset(((ArrayExpr) innernode).getName(),false);
	 	   		}
	 	   		
	// 			if(index<4){
	// 				printlnTab("aload_"+index);
	// 				vcge.push("aload_"+index);
	// 		   	}else if(index >= 4){
	// 		   		printlnTab("aload "+index);
	// 				vcge.push("aload "+index);
	// 		   	}
	 	   		
	 	   		//push index
	 	   		Expr arrayindex = (Expr) ((ArrayExpr) innernode).getIndex().accept(this);
	 	   		
	 	   		int arrindxsize = Integer.parseInt(arrayindex.toString());
	 	   		
	 	   		if(arrindxsize < 6){
	 		   		printlnTab("iconst_"+arrayindex.toString());
	 				vcge.push("iconst_"+arrayindex.toString());
	 	   		}else{
	 	   			printlnTab("iconst "+arrayindex.toString());
	 				vcge.push("iconst "+arrayindex.toString());
	 	   		}
	 			
	 			printlnTab("dup");
	 			
	 			//need to type check...
	 			//use correct load....
	 			if(isPrimitive(((ArrayExpr) innernode).getExprType())){
	 				printlnTab("iaload "+index);
	 	   		}else{
	 	   			printlnTab("aaload "+index);
	 	   		} 
	 				
	 			printlnTab("dup_x2");
	 			vcge.push("dup_x2");
	
	 			printlnTab("iconst -1");
	 			vcge.push("iconst -1");	
	 			
	 			printlnTab("iadd");
	 			vcge.push("iadd"); 
	
	 			vcge.pop();
	 			vcge.pop();
	 			vcge.pop();
	 			
	 			printlnTab("iastore "+index);
	 	   	}
	 	 }
    	
	 	 return null;
   }

    /** Visit a variable expression node
      * @param node the variable expression node
      * @return result of the visit 
      * */
    public Object visit(VarExpr node) {
    	//if there is a reference it has to be "this" 
    	//   because variables are protected
    	boolean hasRef = (node.getRef() != null);
    	if(hasRef) {
    		if(node.getName().equals("length")) {
    			node.getRef().accept(this);
    			printlnTab("arrayLength");
    			vcge.pop(); //pop reference
    		} else {
    			loadFrom(node.getName(),true);
    		}
    	} else {
    		//Calculate Offset
    		loadFrom(node.getName(),false);
    	}
 	   	return node.getExprType();
    }

    /** Visit an array expression node
      * @param node the array expression node
      * @return result of the visit 
      * */
    public Object visit(ArrayExpr node) {
    	boolean hasRef = (node.getRef() != null);
    	String type = vcge.getLocalType(node.getName(), hasRef);
    	
    	type = type.replace("[]", "");
    	node.getIndex().accept(this);
    	vcge.push("index"); 
    	
    	if(type.equals("int") || type.equals("boolean")) {
    		printlnTab("iaload");
    	} else {
    		printlnTab("aaload");
    	}
    	vcge.pop();
    	vcge.pop();
    	vcge.pop();
    	vcge.push("val from " + node.getName());
 	   	return node.getExprType();
    }

    /** Visit an int constant expression node
     * @param node the int constant expression node
     * @return result of the visit 
     * */
   public Object visit(ConstIntExpr node) {
      int expval = node.getIntConstant();
          
	   if(expval <= 5){
		  printlnTab("iconst_"+expval);
		  vcge.push("iconst_"+expval);
	   }else if(expval <= Byte.MAX_VALUE){
		  printlnTab("bipush "+expval);
		  vcge.push("bipush "+expval);
	   }else if(expval <= Short.MAX_VALUE){
		  printlnTab("sipush "+expval);
		  vcge.push("sipush "+expval);
	   }else if (Short.MAX_VALUE < expval){
		  printlnTab("ldc "+expval);
		  vcge.push("ldc "+expval);
	   }

	   return null;
   }

   /** Visit a boolean constant expression node
     * @param node the boolean constant expression node
     * @return result of the visit 
    * @throws IOException 
     * */
   public Object visit(ConstBooleanExpr node){ 
 
	  if(node.getConstant().equals("true")){
		printlnTab("iconst_1");
		vcge.push("iconst_1");
     }else{
    	printlnTab("iconst_0");
    	vcge.push("iconst_0");
     }

     return null;
   }

   /** Visit a string constant expression node
     * @param node the string constant expression node
     * @return result of the visit 
     * */
   public Object visit(ConstStringExpr node) { 
      String str =	node.getConstant();
      
      for (int i = 0; i < str.length(); i ++) {
   	     char cur = str.charAt(i);
   	     if (cur == '\"') 
   	    	 str.replace(str.substring(i), "\\\"");
   	     
   	     if (cur == '\n') 
   	    	 str.replace(str.substring(i), "\\n");
      }
 
      printlnTab("ldc "+ "\"" + str + "\"");
      vcge.push("ldc "+ "\"" + str + "\"");
      
	  return null;
   }
    
    private String getHeaderTypeDescriptor(String type)
    {
    	String descriptor = getTypeDescriptor(type);
    	if(descriptor.charAt(0) == 'L') {
    		descriptor += ";";
    	}
    	return descriptor;
    }
    //Might need work
    private String getTypeDescriptor(String type) 
    {
    	String descriptor = "";
    	if(isArray(type)) {//is Array?
    		type = type.replace("[]","");
    		descriptor += "[";
    	}
    	if(type.equals("int")) {
    		descriptor += "I";
    	} else if(type.equals("boolean")){
    		descriptor += "Z";
    	} else if(type.equals("void")) {
    		descriptor += "V";
    	} else if(type.equals("String") || type.equals("Object")){
    		descriptor += "Ljava/lang/" + type;
    	} else {
    		descriptor += "L" + type;
    	}
    	return descriptor;
    }
    private void removeLastPrint() {
    	lineList.remove(lineList.size());
    }
    private void println(String line) {
    	//System.out.println(lineList.size() + ":" + line);
    	lineList.add(line);
    }
    private void printlnTab(String line){
    	println("      " + line);
    }
    
    static void setClassRoot(ClassTreeNode node) {
    	root = node;
    }
    private void loadFrom(String name, boolean isField) {
    	String type = vcge.getLocalType(name, isField);
    	int offset = vcge.getLocalOffset(name, isField);
		String strOffset = (offset <= 3) ? "_" + offset : "_" + offset;
    	if(isField) {
    		//Get array
    		printlnTab("aload_0");
        	vcge.push("aload_0");
    		printlnTab("getfield " + classNode.getName() + "/" +
    				name + " " + getTypeDescriptor(type));
    		vcge.pop();
    	} else {
    		//Get array
    		if(isPrimitive(type)) {
    			printlnTab("iload" + strOffset);
    		} else {
    			printlnTab("aload" + strOffset);
    		}
	    	
    	}
    	vcge.push(name); 
    }
    private void storeIn(String name, String type, boolean isField, Expr e) {
    	int offset = vcge.getLocalOffset(name, isField);
		String strOffset = (offset <= 3) ? "_" + offset : " " + offset;
    	if(isField) {
        	printlnTab("aload_0");
        	vcge.push("aload_0");
        	e.accept(this);
    		printlnTab("putfield " + className + "/" 
    				+ name + " " + getTypeDescriptor(type));
    		vcge.pop();
    	} else {
    		e.accept(this);
    		System.out.println("Name :" + name);
    		System.out.println("Type :" + type);
    		if(isPrimitive(type)) {
    			printlnTab("istore" + strOffset);
    		} else {
    			printlnTab("astore" + strOffset);
    		}
    	}
    	vcge.pop();
    }
    private boolean isArray(String type) {
    	return type.contains("[]");
    }
    private String convertSMLtoJasmine(String sml) {
    	String header = "";
    	if(!sml.contains("->")) {
    		header = "()" + getHeaderTypeDescriptor(sml);
    	} else {
    		String args = "";
    		String [] split = sml.split("->");
    		if(split[1].contains("*")) {
    			String [] params = split[1].split("*");
    			for(String str : params) 
    				args += getHeaderTypeDescriptor(str);
    		} else {
    			args = split[1];
    		}
    		header = "(" + args + ")" + getHeaderTypeDescriptor(split[0]);
    	}
    	return header;
    }
    private String getMethod(String classRef, String methodName) {
    	String header = "";
    	/*ClassTreeNode node;
    	//What is the method's ref
    	if(ref.equals("super")) {
    		node = classNode.getParent();
    	} else if(ref.equals("this")) {
    		node = classNode;
    	} else { //this is another object
    		node = root.lookupClass(vcge.getLocalType(ref));
    	}
    	while(node.getMethodSymbolTable().peek(method) != null) 
    		node = node.getParent();
    	*/
    	if(classRef.equals("Object") || 
    			classRef.equals("String")) {
    		header = "java/lang/";
    	}
    	header += classRef + "/" + methodName + 
    		convertSMLtoJasmine(
    				(String)root.lookupClass(classRef).
    				getMethodSymbolTable().
    				peek(methodName)
    				);
    	return header;
    }
    boolean isPrimitive(String type) {
    	return (type.equals("int") 
    			|| type.equals("boolean")) ? true : false;
    }
    
}

